package cc.android.testapp.xp.hooker;

import android.app.Application;

import org.json.JSONObject;

import java.lang.reflect.Method;

import cc.android.testapp.cfg.CFG;
import cc.android.testapp.cfg.ConfigHelper;
import cc.android.testapp.util.CLog;
import cc.android.testapp.util.CTool;
import cc.android.testapp.xp.HookBase;
import cc.android.testapp.xp.XPTool;
import cc.android.testapp.xp.HookModule;
import cc.commons.util.StringUtil;
import cc.commons.util.extra.CList;
import cc.commons.util.reflect.FieldUtil;
import cc.commons.util.reflect.MethodUtil;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

@HookModule(packages = "com.cmcc.multipda", name = "网络运维")
public class HookPDA extends HookBase implements ConfigHelper.IConfig {

    @CFG
    private boolean mEnable = false;
    @CFG
    private double mJindu = 0D;
    @CFG
    private double mWeidu = 0D;
    @CFG
    private int mRange = 20;
    @CFG
    private String mCity = "金华市";
    @CFG
    private String mDistrict = "金东区";
    @CFG
    private String mAddress = "";
    @CFG
    private String mDevice = "";
    @CFG
    private boolean mEnableTest = true;

    @Override
    public void onConfigChange(JSONObject pNode) {
        this.mOldCfgStr = CTool.fieldVToStr(this);
        loadAnnoConfig(pNode);
        String tNewStr = CTool.fieldVToStr(this);
        if (!mOldCfgStr.equals(tNewStr)) {
            CLog.log("pda 已更新配置: " + tNewStr);
        }
    }

    @Override
    public String getCfgNode() {
        return "pda";
    }

    @Override
    protected void hookToApp(XC_LoadPackage.LoadPackageParam pParam, Application pApp) throws Throwable {
        ClassLoader tLoader = pApp.getClassLoader();

        CLog.FORCE_FILE_OUTPUT = true;
        try {
            hookRootCheck(tLoader);
            hookBDLocation(tLoader);
            hookDeviceId(tLoader);
            CLog.log("Hook pda success");
        } catch (Throwable e) {
            CLog.log("Error on hook to pda", e);
            this.mEnable = false;
        } finally {
            CLog.FORCE_FILE_OUTPUT = true;
        }
    }

    private void hookDeviceId(ClassLoader pLoader) throws ClassNotFoundException {
        //更改设备机型ID
        final Class<?> tClazz;
        CLog.log("Prepare hook to pda_device_id");
        tClazz = pLoader.loadClass(this.getPackageName() + ".util.DeviceIdUtil");
        Method tTarget = MethodUtil.getMethodIgnoreParam(tClazz, "getDeviceId", true).oneGet();
        XposedBridge.hookMethod(tTarget, new XC_MethodHook() {
            protected void afterHookedMethod(MethodHookParam param) {
                if (!mEnable) return;

                if (StringUtil.isNotEmpty(mDevice)) {
                    param.setResult(mDevice);
                }
            }
        });
        CLog.log("hook pda_device_id end !");
    }

    private void hookBDLocation(ClassLoader pLoader) throws ClassNotFoundException {
        final Class<?> tClazz;
        CLog.log("Prepare hook to bdlocation");
        tClazz = pLoader.loadClass("com.baidu.location.LocationClient");
        XC_MethodHook tHook = new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                Object tLis = param.args[0];
                Class<?> tClazz_BdLoc = pLoader.loadClass("com.baidu.location.BDLocation");
                CList<Method> tMethods = MethodUtil.getDeclaredMethod(tLis.getClass(),
                        (method) -> method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(tClazz_BdLoc));
                XC_MethodHook tCHook = new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        if (!mEnable) return;

                        Object tBDLoc = param.args[0];
                        Class<?> clazz_BDLoc = tBDLoc.getClass();
                        try {
                            int tLocType = (int) MethodUtil.invokeDeclaredMethod(clazz_BDLoc, "getLocType", tBDLoc);
                            if (tLocType == 167) {
                                MethodUtil.invokeDeclaredMethod(clazz_BDLoc, "setLocType", int.class, tBDLoc, 161);
                            }

                            if (mJindu != 0D) {
                                MethodUtil.invokeDeclaredMethod(clazz_BDLoc,
                                        "setLongitude", double.class, tBDLoc, CTool.shakeCoordinate(mJindu, mRange));
                            }
                            if (mWeidu != 0D) {
                                MethodUtil.invokeDeclaredMethod(clazz_BDLoc,
                                        "setLatitude", double.class, tBDLoc, CTool.shakeCoordinate(mWeidu, mRange));
                            }
                            if (StringUtil.isNotEmpty(mAddress)) {
                                Object tAddress = MethodUtil.invokeDeclaredMethod(clazz_BDLoc, "getAddress", tBDLoc);
                                Class<?> clazz_Address = tAddress.getClass();
                                FieldUtil.setFinalFieldValue(FieldUtil.getDeclaredField(clazz_Address, "address"), tAddress, mAddress);
                                if (StringUtil.isNotEmpty(mCity)) {
                                    FieldUtil.setFinalFieldValue(FieldUtil.getDeclaredField(clazz_Address, "city")
                                            , tAddress, mCity);
                                }
                                if (StringUtil.isNotEmpty(mDistrict)) {
                                    FieldUtil.setFinalFieldValue(FieldUtil.getDeclaredField(clazz_Address, "district")
                                            , tAddress, mDistrict);
                                }
                            }
                            //CLog.log("[pda]: change location end");
                        } catch (IllegalStateException exp) {
                            CLog.log("[pda]: change location error", exp);
                        }
                    }
                };
                for (Method sMethod : tMethods) {
                    XposedBridge.hookMethod(sMethod, tCHook);
                }
            }
        };
        Class<?> tClazz2 = pLoader.loadClass("com.baidu.location.BDAbstractLocationListener");
        XPTool.hookNoErr(tClazz, "registerLocationListener", tClazz2, tHook);
        tClazz2 = pLoader.loadClass("com.baidu.location.BDLocationListener");
        XPTool.hookNoErr(tClazz, "registerLocationListener", tClazz2, tHook);
        XPTool.hookNoErr(tClazz, "registerNotifyLocationListener", tClazz2, tHook);

        CLog.log("hook bdlocation end !");
    }

    private void hookRootCheck(ClassLoader pLoader) throws ClassNotFoundException {
        Class<?> tClazz;
        CLog.log("Prepare hook to pda_root");
        tClazz = pLoader.loadClass(this.getPackageName() + ".util.CheckRoot");

        XposedHelpers.findAndHookMethod(tClazz, "isDeviceRooted", XC_MethodReplacement.returnConstant(false));
        CLog.log("hook to pda_root end !");
    }

}
